import type {
  CustomLayerInterface,
  CustomSourceInterface,
  LayerSpecification,
  LayoutSpecification,
  Source,
  SourceSpecification,
} from 'mapbox-gl';
import { Mapless } from '../Base';

type OverrideId<T> = T extends { id?: string }
  ? Omit<T, 'id'> & { id: string | undefined }
  : T;

type AnyLayerSource = {
  source?: LayerSpecification['source'] | SourceSpecification;
};

type StyleSetterOptions = {
  validate?: boolean;
  isInitialLoad?: boolean;
};

type AnyLayerOriginal =
  | (Omit<LayerSpecification, 'source'> & AnyLayerSource)
  | CustomLayerInterface;

type AnyLayer = OverrideId<AnyLayerOriginal>;

type PartSource = Partial<Omit<SourceSpecification, 'type' | 'data'>>;

export type Coordinates = [
  [number, number],
  [number, number],
  [number, number],
  [number, number],
];

declare module '../Base' {
  // TODO 考虑返回mapboxgl.Map | undefined or Mapless or void ??
  interface Mapless {
    getSource(id: string | undefined): Source | undefined;
    addSource(
      id: string | undefined,
      source: SourceSpecification | CustomSourceInterface<unknown>,
    ): mapboxgl.Map | undefined;
    addEmptySource(
      id: string | undefined,
      options?: PartSource,
    ): mapboxgl.Map | undefined;
    setData(
      id: string | undefined,
      data: GeoJSON.GeoJSON | string,
    ): mapboxgl.Map | undefined;
    setEmptyData(id: string | undefined): mapboxgl.Map | undefined;
    getLayer<T extends LayerSpecification | CustomLayerInterface>(
      id: string | undefined,
    ): T | undefined;
    addLayer(layer: AnyLayer, beforeId?: string): mapboxgl.Map | undefined;
    setLayoutProperty<T extends keyof LayoutSpecification>(
      layerId: string | undefined,
      name: T,
      value: LayoutSpecification[T],
      options?: StyleSetterOptions,
    ): mapboxgl.Map | undefined;
    removeLayer(id: string | undefined): mapboxgl.Map | undefined;
    removeSource(id: string | undefined): mapboxgl.Map | undefined;
    setCursor(cursor: string): void;
    updateImage(
      id: string | undefined,
      options: { url: string; coordinates?: Coordinates },
    ): mapboxgl.Map | undefined;
  }
}

Mapless.prototype.getSource = function (id: string | undefined) {
  if (!id) return undefined;
  return this.map?.getSource(id);
};

Mapless.prototype.addSource = function (
  id: string | undefined,
  source: SourceSpecification | CustomSourceInterface<unknown>,
) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  if (this.map.getSource(id)) return undefined;
  this.map.addSource(id, source);
  return this.map;
};

Mapless.prototype.addEmptySource = function (
  id: string | undefined,
  options?: PartSource,
) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  this.map.addSource(id, {
    type: 'geojson',
    data: {
      type: 'FeatureCollection',
      features: [],
    },
    ...options,
  });
  return this.map;
};

Mapless.prototype.setData = function (
  id: string | undefined,
  data: GeoJSON.GeoJSON | string,
) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  const source = this.map?.getSource(id) as mapboxgl.GeoJSONSource;
  source?.setData(data);
  return this.map;
};

Mapless.prototype.setEmptyData = function (id: string | undefined) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  const source = this.map?.getSource(id) as mapboxgl.GeoJSONSource;
  source?.setData({
    type: 'FeatureCollection',
    features: [],
  });
  return this.map;
};

Mapless.prototype.getLayer = function <
  T extends LayerSpecification | CustomLayerInterface,
>(id: string | undefined): T | undefined {
  if (!id) return undefined;
  return this.map?.getLayer(id) as T | undefined;
};

Mapless.prototype.addLayer = function (layer: AnyLayer, beforeId?: string) {
  if (!layer.id) return undefined;
  if (!this.map) return undefined;
  if (this.map.getLayer(layer.id)) return undefined;
  this.map.addLayer(layer as AnyLayerOriginal, beforeId);
  return this.map;
};

Mapless.prototype.setLayoutProperty = function <
  T extends keyof LayoutSpecification,
>(
  layerId: string | undefined,
  name: T,
  value: LayoutSpecification[T],
  options?: StyleSetterOptions,
) {
  if (!layerId) return undefined;
  if (!this.map) return undefined;
  this.map.setLayoutProperty(layerId, name, value, options);
  return this.map;
};

Mapless.prototype.removeLayer = function (id: string | undefined) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  if (!this.map.getLayer(id)) return undefined;
  this.map.removeLayer(id);
  return this.map;
};

Mapless.prototype.removeSource = function (id: string | undefined) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  if (!this.map.getSource(id)) return undefined;
  this.map.removeSource(id);
  return this.map;
};

Mapless.prototype.setCursor = function (cursor: string) {
  if (!this.map) return;
  if (this.map.getCanvas()) {
    this.map.getCanvas().style.cursor = cursor;
  }
};

Mapless.prototype.updateImage = function (
  id: string | undefined,
  options: { url: string; coordinates?: Coordinates },
) {
  if (!id) return undefined;
  if (!this.map) return undefined;
  const { url, coordinates } = options;
  const source = this.map.getSource(id) as mapboxgl.ImageSource;
  source?.updateImage({
    url,
    coordinates,
  });
  return this.map;
};
